package com.aratana.ui.fields;

import java.awt.Color;
import java.awt.event.FocusEvent;
import java.awt.event.ItemEvent;
import java.awt.event.ItemListener;
import java.util.ArrayList;
import java.util.List;

import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.MutableComboBoxModel;

import com.aratana.ui.InputChangeListener;
import com.aratana.ui.ValueChangeListener;
import com.aratana.ui.ViewField;

@SuppressWarnings("serial")
public class ComboField<T> extends JComboBox implements ViewField<T> {

	private Color originalBackground;

	private final List<ValueChangeListener<T>> listeners = new ArrayList<ValueChangeListener<T>>();
	private final List<InputChangeListener> input = new ArrayList<InputChangeListener>();

	private final Class<T> fieldClass;

	public ComboField(Class<T> comboClass) {
		fieldClass = comboClass;
		setFont(TAHOMA_13);

		addItemListener(new ItemListener() {

			T old = null;

			@SuppressWarnings("unchecked")
			@Override
			public void itemStateChanged(ItemEvent e) {
				if (e.getStateChange() == ItemEvent.SELECTED) {
					fireValueChanged((T) e.getItem(), old);
				} else {
					old = (T) e.getItem();
					fireInputChanged();
				}
			}
		});

		setModel(new DefaultComboBoxModel() {
			@Override
			public void addElement(Object anObject) {
				if (anObject == null || fieldClass.isAssignableFrom(anObject.getClass())) {
					super.addElement(anObject);
				} else {
					throw new RuntimeException("Can't add another type to ComboField<" + fieldClass.toString().substring(6) + "> ('" + anObject.getClass().toString().substring(6) + "').");
				}
			}
		});
	}

	@Override
	public void addValueChangeListener(final ValueChangeListener<T> listener) {
		listeners.add(listener);
	}

	public boolean removeValueChangeListener(final ValueChangeListener<T> listener) {
		return listeners.remove(listener);
	}

	public void removeAllValueChangeListener(final ValueChangeListener<T> listener) {
		listeners.clear();
	}

	protected boolean validateValue(T newValue) {
		boolean valid = true;

		for (ValueChangeListener<T> l : listeners) {
			valid = l.validateValue(newValue);
			if (!valid) {
				break;
			}
		}

		return valid;
	}

	protected void fireValueChanged(T newValue, T oldValue) {
		for (ValueChangeListener<T> l : listeners) {
			l.valueChanged(newValue, oldValue);
		}
	}
	
	@Override
	public void addInputChangeListener(InputChangeListener listener) {
		input.add(listener);
	}

	public boolean removeInputChangeListener(final InputChangeListener listener) {
		return input.remove(listener);
	}

	public void removeAllInputChangeListener() {
		input.clear();
	}
	
	protected void fireInputChanged() {
		for (InputChangeListener l : input) {
			l.inputChange(this);
		}
	}

	@Override
	public Class<T> getFieldClass() {
		return fieldClass;
	}

	@Override
	public int getMax() {
		return 0;
	}

	@Override
	public int getMin() {
		return 0;
	}

	@Override
	public T getStringValue(String str) throws Exception {
		return null;
	}

	@SuppressWarnings("unchecked")
	@Override
	public T getValue() {
		return (T) getSelectedItem();
	}

	@Override
	public String getValueString(T value) {
		return value == null ? null : value.toString();
	}

	@Override
	public boolean isObrigatory() {
		return false;
	}

	@Override
	public T setValue(T value) {
		setSelectedItem(value);
		return getValue();
	}

	public void add(T value) {
		((MutableComboBoxModel) getModel()).addElement(value);
	}

	public void addAll(List<T> values) {
		for (T value : values) {
			((MutableComboBoxModel) getModel()).addElement(value);
		}
	}

	public void addAll(T... values) {
		for (T value : values) {
			((MutableComboBoxModel) getModel()).addElement(value);
		}
	}

	@Override
	protected void processFocusEvent(final FocusEvent e) {
		if (e.getID() == FocusEvent.FOCUS_GAINED && isEditable()) {
			if (originalBackground == null) {
				originalBackground = getBackground();
			}
			setBackground(SELECTED_BACKGROUND);
			repaint();
		} else if (originalBackground != null) {
			setBackground(originalBackground);
			originalBackground = null;
			repaint();
		}
		super.processFocusEvent(e);
	}

}
